import SparkMD5 from "spark-md5";
import { uploadChunk, mergeChunks } from "@/api/file";
import { AxiosResponse } from "axios";

const calculateHash = async (
  chunks: ChunkObject[],
  chunkSize: number
): Promise<string> => {
  return new Promise<string>((resolve, reject) => {
    try {
      const targets: Blob[] = [];
      const spark = new SparkMD5.ArrayBuffer();
      const fileReader = new FileReader();
      fileReader.onload = (e) => {
        if (e.target) {
          spark.append(e.target.result as ArrayBuffer);
          resolve(spark.end());
        }
      };
      chunks.forEach((chunk, index) => {
        if (index === 0 || index === chunks.length - 1) {
          targets.push(chunk.blob);
        } else {
          targets.push(chunk.blob.slice(0, 2));
          targets.push(chunk.blob.slice(chunkSize / 2, chunkSize / 2 + 2));
          targets.push(chunk.blob.slice(chunkSize - 2, chunkSize));
        }
      });
      fileReader.readAsArrayBuffer(new Blob(targets));
    } catch (err) {
      reject(err);
    }
  });
};

const cutFile = async (file: File, chunkSize: number = 1024 * 1024 * 2) => {
  const chunkCount = Math.ceil(file.size / chunkSize);
  const chunks: ChunkObject[] = [];
  for (let i = 0; i < chunkCount; i++) {
    const chunk = file.slice(i * chunkSize, (i + 1) * chunkSize);
    chunks.push({
      blob: chunk,
      index: i,
      hash: "",
    });
  }
  const hash = await calculateHash(chunks, chunkSize);
  chunks.forEach((chunk, index) => {
    chunk.hash = `${hash}-${index}`;
  });
  return {
    chunks,
    hash,
  };
};

export const uploadFile = async (file: File): Promise<AxiosResponse | null> => {
  return new Promise(async (resolve, reject) => {
    try {
      const { chunks, hash } = await cutFile(file);
      const taskPool: Promise<any>[] = [];
      const maxRequest = 4;
      let index = 0;
      while (index < chunks.length) {
        const formData = new FormData();
        formData.append("fileHash", hash);
        formData.append("chunkHash", chunks[index].hash);
        formData.append("chunk", chunks[index].blob);
        const task = uploadChunk(formData);
        task.then(() => {
          taskPool.splice(
            taskPool.findIndex((item) => item === task),
            1
          );
        });
        taskPool.push(task);
        if (taskPool.length === maxRequest) {
          await Promise.race(taskPool);
        }
        index++;
      }
      await Promise.all(taskPool);
      const result = await mergeChunks({
        fileHash: hash,
        chunkSize: 1024 * 1024 * 2,
        fileType: file.name.replace(/^.*\./, ""),
        fileName: file.name,
      });
      if (result.data.code === 200) {
        resolve(result);
      }
    } catch (error) {
      reject(null);
    }
  });
};
